Expression shipping
クライアントとサービスの境界を越えたリッチなクエリをサポートするために、ODataエンドポイントがすべてのクエリAPIサーフェスとなるような設計は避けました。RESTスタイルのパス式によるエンティティのトラバーサルは、APIサーフェスとしては有効ですが、複雑な結合、非自明な述語や投影の表現などを含むリッチなクエリ機能を提供することは、このようなプロトコルの強みではありません。その代わりに、クライアントからサービスへの式ツリーの転送機能に大きく依存しています。REST や OData のサポートなどの補完的な API サーフェスは、基盤となるクエリ言語とクエリプロセッサの実装の上に重ねられています。
In order to support rich querying across the client-service boundary, we steered away from a design where an OData endpoint is the end-all be-all querying API surface. While REST-style traversal of entities through path expressions is a valid API surface to offer, offering rich query capabilities involving complex joins, expressing non-trivial predicates and projections, etc. is not the strength of such protocols. Instead, we rely heavily on the ability to ship expression trees from client to service. Complementary API surfaces, such as REST and/or OData support, were layered on top of the underlying query language and query processor implementation.
式の木の出荷の実装は、当初、LINQ to Everythingの作業に基づいていました。(これには、System.Linq.Expressionsオブジェクトモデル用のXMLベースおよびJSONベースのシリアライズ機能が含まれていました。このアプローチはうまく機能しましたが、リフレクション・オブジェクト・モデル(すなわち、タイプ、メンバー、およびアセンブリ)の逐語的な表現のために、クライアントとサービスの間のやや緊密な結合に悩まされました。これは、クライアントとサービスがいくつかのアセンブリを共有しなければならないことを意味します。
The implementation of expression tree shipping was initially based on the LINQ to Everything work (cf. the “reactive chat service” demo and IQbservable<T> work) which included XML-based and JSON-based serialization functionality for the System.Linq.Expressions object model. While this approach worked well, it suffered from somewhat tight coupling between clients and services due to the verbatim representation of the reflection object model (i.e. types, members, and assemblies). This meant that clients and services had to share some assemblies.
この結合を解消するために、Reaqtorは式木の正規化ステップを追加し、CLRの型システムを抽象化した「Bonsai」と呼ばれるシリアライズフォーマットを導入しました。Bonsaiについては後で詳しく説明しますが、LINQ to Everything時代の式の配送に関する作業が、この作業の基礎となったことだけは言っておきます。実際、正規化された表現への最初の一歩は、グラフデータベースの取り組みによって踏み出されました。
To break this coupling, Reaqtor introduced additional expression tree normalization steps, and a serialization format called “Bonsai” which abstracts away the CLR type system. We’ll discuss Bonsai in more detail later, but suffice it to say that the work on expression shipping in the LINQ to Everything days provided the foundation for this work. In fact, a first step towards a normalized representation was taken by the graph database effort, namely to move to a standardized data model which is discussed further on.
CypherやSPARQLのような多くのグラフ固有の外部ドメイン固有言語(DSL)を最終的にサポートするように設計されているだけでなく、式木出荷機能を使用することで一石二鳥となりました。まず、汎用的な式木表現(型付きラムダ計算に、算術演算子などの有名な構成要素に特化したノードを追加したもの)を使用できるようになり、別の中間言語の導入を避けることができました。また、C#、Visual Basic、F#などの上位言語でLINQ構文を使用して内部DSLをサポートするために、かなり軽量化された実装になっています。内部および外部DSLの定義については、Martin Fowler氏を参照してください。
Besides designing for eventual support for many graph-specific external Domain Specific Languages (DSLs) such as Cypher and SPARQL, the use of expression tree shipping facilities killed two birds with one stone. First, it enabled the use of general-purpose expression tree representations (which can be reduced to typed lambda calculus with some specialized nodes for well-known constructs such as arithmetic operators), thus avoiding the introduction of yet another intermediate language. Second, it also resulted in a fairly trivial implementation for internal DSL support using LINQ syntax in higher-level languages such as C#, Visual Basic, and F#. See Martin Fowler for a definition of internal and external DSLs.
従来のLINQプロバイダは、既存のクエリ言語の外部DSL(SQLなど)と、C#、Visual Basic、F#のLINQベースのクエリ式内部DSLとの間の変換を常に扱ってきましたが、グラフデータベースでのLINQの使い方は根本的に異なります。LINQの式木を翻訳するために新たな言語を開発するのではなく、ホスト言語のいずれかから式木に漏れたクセを取り除くために正規化処理を施した後、この表現をワイヤー上に配置しました。これにより、(ツリーベースの表現ではなく)構文の選択、型システムや型チェッカーなど、新しい言語の開発に伴う複雑な問題の多くを即座に回避することができました(IntelliSenseなどの機能を使ってコードを編集するためのIDEの経験は言うまでもありません)。
While traditional LINQ providers have always dealt with translation between an existing query language external DSL (such as SQL) and the LINQ-based query expression internal DSL in C#, Visual Basic, and F#, the graph database’s use of LINQ was fundamentally different. Rather than inventing a new language to translate LINQ expression trees into, we put this representation on the wire, after applying some normalization steps to shake off quirks that leaked into the representation from any of the host languages. This immediately side-stepped many of the complexities involved in inventing new languages, including syntactic choices (rather than a tree-based representation), type systems and type checkers, etc. (not to mention any IDE experience to edit code with features such as IntelliSense and whatnot).